#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
视频压缩核心模块
提供视频压缩的核心功能
"""

import os
import ffmpeg
import subprocess
import platform
from typing import Dict, Any, Optional, Callable
from config.settings import (
    COMPRESSION_PRESETS, DEFAULT_OUTPUT_SUFFIX,
    PERFORMANCE_SETTINGS, HARDWARE_ENCODERS, GPU_ACCELERATION
)


class VideoCompressor:
    """视频压缩器类"""
    
    def __init__(self):
        """初始化压缩器"""
        self.is_compressing = False
        self.progress_callback: Optional[Callable] = None
        self.available_encoders = []
        self.best_encoder = "libx264"  # 默认CPU编码器
        self.gpu_type = None

        # 检测可用的硬件加速
        self._detect_hardware_acceleration()
    
    def set_progress_callback(self, callback: Callable):
        """设置进度回调函数"""
        self.progress_callback = callback

    def _detect_hardware_acceleration(self):
        """检测可用的硬件加速"""
        try:
            # 获取FFmpeg支持的编码器列表
            result = subprocess.run(['ffmpeg', '-encoders'],
                                  capture_output=True, text=True, timeout=10)

            if result.returncode == 0:
                encoders_output = result.stdout

                # 检测可用的硬件编码器
                for encoder in HARDWARE_ENCODERS:
                    if encoder in encoders_output:
                        self.available_encoders.append(encoder)

                # 选择最佳编码器
                if self.available_encoders:
                    self.best_encoder = self.available_encoders[0]

                    # 检测GPU类型
                    if "nvenc" in self.best_encoder:
                        self.gpu_type = "nvidia"
                    elif "amf" in self.best_encoder:
                        self.gpu_type = "amd"
                    elif "qsv" in self.best_encoder:
                        self.gpu_type = "intel"

                print(f"检测到的编码器: {self.available_encoders}")
                print(f"选择的编码器: {self.best_encoder}")
                if self.gpu_type:
                    print(f"GPU类型: {self.gpu_type}")

        except Exception as e:
            print(f"硬件检测失败，使用CPU编码: {str(e)}")
            self.best_encoder = "libx264"

    def get_optimal_encoder_settings(self, preset_name: str) -> Dict[str, Any]:
        """获取最优的编码器设置"""
        preset = COMPRESSION_PRESETS[preset_name]
        settings = {
            'vcodec': self.best_encoder,
            'acodec': 'aac',
            'audio_bitrate': '128k',
            'threads': preset.get('threads', 0),
            'progress': 'pipe:2'
        }

        # 根据编码器类型设置参数
        if self.best_encoder == "libx264":
            # CPU编码设置
            settings.update({
                'crf': preset['crf'],
                'preset': preset['preset'],
                'x264opts': 'threads=0:sliced-threads=1'  # 优化多线程
            })
            if preset.get('tune'):
                settings['tune'] = preset['tune']

        elif "nvenc" in self.best_encoder:
            # NVIDIA GPU编码设置
            gpu_settings = GPU_ACCELERATION['nvidia']
            settings.update({
                'preset': gpu_settings['preset'],
                'tune': gpu_settings['tune'],
                'cq': preset['crf'],  # NVENC使用cq而不是crf
                'rc': 'vbr',  # 可变比特率
                'surfaces': '32'  # 增加表面数量提高性能
            })

        elif "amf" in self.best_encoder:
            # AMD GPU编码设置
            gpu_settings = GPU_ACCELERATION['amd']
            settings.update({
                'preset': gpu_settings['preset'],
                'quality': gpu_settings['quality'],
                'crf': preset['crf']
            })

        elif "qsv" in self.best_encoder:
            # Intel GPU编码设置
            gpu_settings = GPU_ACCELERATION['intel']
            settings.update({
                'preset': gpu_settings['preset'],
                'look_ahead': gpu_settings['look_ahead'],
                'crf': preset['crf']
            })

        return settings
    
    def get_video_info(self, input_path: str) -> Dict[str, Any]:
        """获取视频文件信息"""
        try:
            probe = ffmpeg.probe(input_path)
            video_stream = next((stream for stream in probe['streams'] 
                               if stream['codec_type'] == 'video'), None)
            
            if not video_stream:
                raise ValueError("未找到视频流")
            
            # 获取文件大小
            file_size = os.path.getsize(input_path)
            
            info = {
                'duration': float(probe['format']['duration']),
                'width': int(video_stream['width']),
                'height': int(video_stream['height']),
                'codec': video_stream['codec_name'],
                'bitrate': int(probe['format']['bit_rate']) if 'bit_rate' in probe['format'] else None,
                'file_size': file_size,
                'file_size_mb': round(file_size / (1024 * 1024), 2)
            }
            
            return info
            
        except Exception as e:
            raise Exception(f"获取视频信息失败: {str(e)}")
    
    def compress_video(self, input_path: str, output_path: str,
                      preset_name: str = "medium_quality") -> Dict[str, Any]:
        """
        压缩视频文件

        Args:
            input_path: 输入文件路径
            output_path: 输出文件路径
            preset_name: 压缩预设名称

        Returns:
            压缩结果信息
        """
        if not os.path.exists(input_path):
            raise FileNotFoundError(f"输入文件不存在: {input_path}")

        if preset_name not in COMPRESSION_PRESETS:
            raise ValueError(f"未知的压缩预设: {preset_name}")

        preset = COMPRESSION_PRESETS[preset_name]

        try:
            # 只有在没有进行批量压缩时才设置状态
            was_compressing = self.is_compressing
            if not was_compressing:
                self.is_compressing = True

            # 检查输出目录是否存在
            output_dir = os.path.dirname(output_path)
            if not os.path.exists(output_dir):
                os.makedirs(output_dir)

            # 获取原始文件信息
            original_info = self.get_video_info(input_path)

            # 检查磁盘空间（简单检查）
            if hasattr(os, 'statvfs'):  # Unix系统
                stat = os.statvfs(output_dir)
                free_space = stat.f_bavail * stat.f_frsize
                if free_space < original_info['file_size']:
                    raise Exception("磁盘空间不足")

            # 获取优化的编码器设置
            encoder_settings = self.get_optimal_encoder_settings(preset_name)

            # 构建FFmpeg命令
            input_stream = ffmpeg.input(input_path)

            # 添加硬件解码（如果可用）
            if self.gpu_type == "nvidia" and PERFORMANCE_SETTINGS['enable_gpu_acceleration']:
                input_stream = ffmpeg.input(input_path, hwaccel='cuda', hwaccel_output_format='cuda')

            output_stream = ffmpeg.output(input_stream, output_path, **encoder_settings)

            # 执行压缩
            try:
                self._run_ffmpeg_with_progress(output_stream, original_info['duration'])
            except ffmpeg.Error as e:
                # 解析FFmpeg错误信息
                error_msg = "FFmpeg压缩失败"
                if e.stderr:
                    # 安全地解码错误信息
                    try:
                        if isinstance(e.stderr, bytes):
                            stderr_str = e.stderr.decode('utf-8', errors='ignore')
                        else:
                            stderr_str = str(e.stderr)
                    except:
                        stderr_str = "无法解析错误信息"

                    if "No space left on device" in stderr_str:
                        error_msg = "磁盘空间不足"
                    elif "Permission denied" in stderr_str:
                        error_msg = "文件权限错误"
                    elif "Invalid data found" in stderr_str:
                        error_msg = "视频文件损坏或格式不支持"
                    else:
                        error_msg = f"FFmpeg错误: {stderr_str[:200]}"
                raise Exception(error_msg)

            # 检查输出文件是否生成
            if not os.path.exists(output_path):
                raise Exception("压缩失败，未生成输出文件")

            # 获取压缩后文件信息
            compressed_info = self.get_video_info(output_path)

            # 计算压缩比例
            compression_ratio = (1 - compressed_info['file_size'] / original_info['file_size']) * 100

            result = {
                'success': True,
                'original_size': original_info['file_size'],
                'compressed_size': compressed_info['file_size'],
                'original_size_mb': original_info['file_size_mb'],
                'compressed_size_mb': compressed_info['file_size_mb'],
                'compression_ratio': round(compression_ratio, 2),
                'preset_used': preset_name,
                'output_path': output_path
            }

            return result

        except Exception as e:
            # 清理可能生成的不完整文件
            if os.path.exists(output_path):
                try:
                    os.remove(output_path)
                except:
                    pass
            raise Exception(f"视频压缩失败: {str(e)}")

        finally:
            # 只有在单独压缩时才重置状态
            if not was_compressing:
                self.is_compressing = False
    
    def generate_output_path(self, input_path: str, output_dir: str = None) -> str:
        """
        生成输出文件路径

        Args:
            input_path: 输入文件路径
            output_dir: 输出目录，如果为None则使用输入文件所在目录

        Returns:
            输出文件路径
        """
        from utils.file_handler import get_safe_filename

        input_dir = os.path.dirname(os.path.abspath(input_path))
        filename = os.path.basename(input_path)
        name, ext = os.path.splitext(filename)

        # 如果没有指定输出目录，使用输入文件所在目录
        if output_dir is None or output_dir == "":
            output_dir = input_dir if input_dir else os.getcwd()

        # 生成安全的文件名
        safe_name = get_safe_filename(name)
        output_filename = f"{safe_name}{DEFAULT_OUTPUT_SUFFIX}{ext}"
        output_path = os.path.join(output_dir, output_filename)
        
        # 如果文件已存在，添加数字后缀
        counter = 1
        while os.path.exists(output_path):
            output_filename = f"{name}{DEFAULT_OUTPUT_SUFFIX}_{counter}{ext}"
            output_path = os.path.join(output_dir, output_filename)
            counter += 1
        
        return output_path
    
    def compress_batch(self, file_list: list, output_dir: str,
                      preset_name: str = "medium_quality") -> Dict[str, Any]:
        """
        批量压缩视频文件

        Args:
            file_list: 输入文件路径列表
            output_dir: 输出目录
            preset_name: 压缩预设名称

        Returns:
            批量压缩结果信息
        """
        results = {
            'total_files': len(file_list),
            'successful': 0,
            'failed': 0,
            'results': [],
            'total_original_size': 0,
            'total_compressed_size': 0
        }

        self.is_compressing = True  # 设置压缩状态

        for i, input_file in enumerate(file_list):
            if not self.is_compressing:
                break

            try:
                filename = os.path.basename(input_file)

                # 保存原始回调
                original_callback = self.progress_callback

                # 设置当前文件的进度回调
                def file_progress_callback(file_progress, message):
                    # 计算总体进度：已完成文件的进度 + 当前文件的进度
                    completed_files_progress = (i / len(file_list)) * 100
                    current_file_progress = (file_progress / len(file_list))
                    total_progress = completed_files_progress + current_file_progress

                    if original_callback:  # 使用原始回调，避免递归
                        # 提供更详细的信息用于时间预估
                        detailed_message = f"文件 {i+1}/{len(file_list)}: {filename} - {message}"
                        original_callback(total_progress, detailed_message)

                # 临时设置进度回调
                self.progress_callback = file_progress_callback

                output_path = self.generate_output_path(input_file, output_dir)
                result = self.compress_video(input_file, output_path, preset_name)

                # 恢复原始回调
                self.progress_callback = original_callback

                if result['success']:
                    results['successful'] += 1
                    results['total_original_size'] += result['original_size']
                    results['total_compressed_size'] += result['compressed_size']

                    results['results'].append({
                        'file': os.path.basename(input_file),
                        'status': 'success',
                        'original_size': result['original_size'],
                        'compressed_size': result['compressed_size'],
                        'compression_ratio': result['compression_ratio'],
                        'output_path': result['output_path']
                    })
                else:
                    results['failed'] += 1
                    results['results'].append({
                        'file': os.path.basename(input_file),
                        'status': 'failed',
                        'error': '压缩失败'
                    })

            except Exception as e:
                # 确保恢复原始回调
                if 'original_callback' in locals():
                    self.progress_callback = original_callback

                results['failed'] += 1
                results['results'].append({
                    'file': os.path.basename(input_file),
                    'status': 'failed',
                    'error': str(e)
                })

        # 计算总体压缩比例
        if results['total_original_size'] > 0:
            total_compression_ratio = (1 - results['total_compressed_size'] / results['total_original_size']) * 100
            results['total_compression_ratio'] = round(total_compression_ratio, 2)
        else:
            results['total_compression_ratio'] = 0

        # 重置压缩状态
        self.is_compressing = False

        return results

    def _run_ffmpeg_with_progress(self, output_stream, total_duration):
        """
        运行FFmpeg并监控进度

        Args:
            output_stream: FFmpeg输出流
            total_duration: 视频总时长（秒）
        """
        import subprocess
        import re
        import time
        import threading

        # 构建FFmpeg命令
        cmd = ffmpeg.compile(output_stream, overwrite_output=True)

        # 启动FFmpeg进程
        process = subprocess.Popen(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=False,
            bufsize=1
        )

        # 设置进程优先级（Windows）
        if platform.system() == "Windows":
            try:
                import psutil
                p = psutil.Process(process.pid)
                priority_map = {
                    "low": psutil.BELOW_NORMAL_PRIORITY_CLASS,
                    "normal": psutil.NORMAL_PRIORITY_CLASS,
                    "high": psutil.HIGH_PRIORITY_CLASS
                }
                priority = PERFORMANCE_SETTINGS.get('thread_priority', 'normal')
                if priority in priority_map:
                    p.nice(priority_map[priority])
            except ImportError:
                pass  # psutil不可用时跳过

        # 用于存储输出和进度信息
        stderr_output = b""
        last_progress = -1
        last_update_time = 0

        def read_stderr():
            """在单独线程中读取stderr输出"""
            nonlocal stderr_output, last_progress, last_update_time

            buffer = b""
            while True:
                try:
                    # 读取一个字节
                    byte = process.stderr.read(1)
                    if not byte:
                        break

                    buffer += byte
                    stderr_output += byte

                    # 当遇到换行符或缓冲区足够大时处理
                    if byte == b'\n' or len(buffer) > 100:
                        try:
                            line = buffer.decode('utf-8', errors='ignore')
                            buffer = b""

                            # 解析进度信息
                            current_time = time.time()
                            if current_time - last_update_time >= 0.5:  # 每0.5秒最多更新一次
                                progress = self._parse_progress(line, total_duration)
                                if progress is not None and progress != last_progress:
                                    if self.progress_callback:
                                        self.progress_callback(progress, f"压缩进度: {progress:.1f}%")
                                    last_progress = progress
                                    last_update_time = current_time
                        except:
                            buffer = b""
                            continue

                except Exception:
                    break

        # 启动读取线程
        read_thread = threading.Thread(target=read_stderr, daemon=True)
        read_thread.start()

        # 等待进程完成
        while process.poll() is None:
            if not self.is_compressing:
                # 如果停止压缩，终止进程
                process.terminate()
                process.wait()
                raise Exception("压缩被用户停止")
            time.sleep(0.1)

        # 等待读取线程完成
        read_thread.join(timeout=2)

        return_code = process.returncode

        if return_code != 0:
            # 解码错误输出
            try:
                error_text = stderr_output.decode('utf-8', errors='ignore')
            except:
                error_text = "无法解析错误信息"
            raise ffmpeg.Error('ffmpeg', '', error_text)

    def _parse_progress(self, line, total_duration):
        """解析FFmpeg输出中的进度信息"""
        import re

        try:
            # 查找不同格式的时间信息
            # 格式1: time=00:01:23.45
            time_match = re.search(r'time=(\d+):(\d+):(\d+)\.(\d+)', line)
            if time_match:
                hours = int(time_match.group(1))
                minutes = int(time_match.group(2))
                seconds = int(time_match.group(3))
                current_seconds = hours * 3600 + minutes * 60 + seconds

                if total_duration > 0:
                    progress = min((current_seconds / total_duration) * 100, 100)
                    return progress

            # 格式2: out_time_ms=83450000 (微秒)
            ms_match = re.search(r'out_time_ms=(\d+)', line)
            if ms_match:
                current_ms = int(ms_match.group(1))
                current_seconds = current_ms / 1000000  # 微秒转秒

                if total_duration > 0:
                    progress = min((current_seconds / total_duration) * 100, 100)
                    return progress

            # 格式3: out_time=00:01:23.450000
            time_match2 = re.search(r'out_time=(\d+):(\d+):(\d+)\.(\d+)', line)
            if time_match2:
                hours = int(time_match2.group(1))
                minutes = int(time_match2.group(2))
                seconds = int(time_match2.group(3))
                current_seconds = hours * 3600 + minutes * 60 + seconds

                if total_duration > 0:
                    progress = min((current_seconds / total_duration) * 100, 100)
                    return progress

        except Exception:
            pass

        return None

    def stop_compression(self):
        """停止压缩（目前的简单实现）"""
        self.is_compressing = False
